﻿using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Microsoft.Practices.Unity;
using MiniStock.BusinessLogic.EventProcessor;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using MiniStock.BusinessLogic.EventProcessor.EventRepository;
using MiniStock.BusinessLogic.EventQueue.Queue;
using MiniStock.Entities.Common.Events;
using MiniStock.Entities.Stock.Events;
using MiniStock.Entities.Stock.Events.Customer;
using MiniStock.Infrastructure;
using MiniStock.Infrastructure.Configurator;
using MiniStock.Infrastructure.Implementation;
using MiniStock.Infrastructure.Queue;

namespace MiniStock.BusinessLogic.EventProcessor.Test
{
    
    
    /// <summary>
    ///This is a test class for EventQueueWorkerTest and is intended
    ///to contain all EventQueueWorkerTest Unit Tests
    ///</summary>
    [TestClass()]
    public class EventQueueWorkerTest
    {

        public class MockEventProcessorWorker: EventProcessorWorker
        {
            public static List<MockEventProcessorWorker> Workers = new List<MockEventProcessorWorker>();
 
            public MockEventProcessorWorker()
            {
                Workers.Add(this);
            }

            public AutoResetEvent PulseEvent = new AutoResetEvent(false);
            public ManualResetEvent ProcessedEvent = new ManualResetEvent(false);

            public class EventDescriptor
            {
                public Type EventType { get; set; }
                public string EntityHash { get; set; }
            }

            public readonly Queue<EventDescriptor> Events = new Queue<EventDescriptor>();

            public override bool DoWork()
            {
                if (0 < WaitHandle.WaitAny(new WaitHandle[]
                                           {
                                               PulseEvent, 
                                               StopEvent,
                                           }, 60000)
                )
                    return false;

                var e = InputEventQueue.Dequeue();
                if(e == null) return false;

                if(e is EntityEventBase)
                    PopEventId(e);
                ProcessedEvent.Set();

                if(Events.Count == 0)
                    return true;

                var expected = Events.Dequeue();

                Assert.AreEqual(expected.EventType, e.GetType(), "Event type mismatch");
                if(e is EntityEventBase) {
                    Assert.AreEqual(expected.EntityHash, ((EntityEventBase)e).GetEntityHash(), "Entity hash mismatch");
                }
                return true;
            }
        }


        private TestContext testContextInstance;

        /// <summary>
        ///Gets or sets the test context which provides
        ///information about and functionality for the current test run.
        ///</summary>
        public TestContext TestContext
        {
            get
            {
                return testContextInstance;
            }
            set
            {
                testContextInstance = value;
            }
        }

        public static IUnityContainer RootContainer;

        #region Additional test attributes
        // 
        //You can use the following additional attributes as you write your tests:
        //
        //Use ClassInitialize to run code before running the first test in the class
        //[ClassInitialize()]
        //public static void MyClassInitialize(TestContext testContext)
        //{
        //}
        //
        //Use ClassCleanup to run code after all tests in a class have run
        //[ClassCleanup()]
        //public static void MyClassCleanup()
        //{
        //}
        //
        //Use TestInitialize to run code before running each test
        [TestInitialize()]
        public void MyTestInitialize()
        {
            var mc = new MemoryConfigurator();
            RootContainer = new UnityContainer();
            RootContainer.RegisterType<IEventQueue, MemoryQueue>(new ContainerControlledLifetimeManager());
            RootContainer.RegisterType<IEventQueue, MemoryQueue>("InternalQueue", new ContainerControlledLifetimeManager());
            RootContainer.RegisterType<IRepository<EventProcessorEventData>, DummyRepository<EventProcessorEventData>>(new ContainerControlledLifetimeManager());
            RootContainer.RegisterType<EventProcessorWorker, MockEventProcessorWorker>();
            RootContainer.RegisterType<IEventQueueConfigurator, TestQueueConfigurator>();
            RootContainer.RegisterType<IEventQueueConfigurator, TestQueueConfigurator>("InternalConfigurator");
            //RootContainer.RegisterType<IEventProcessorConfigurator, MemoryConfigurator>();
            //RootContainer.RegisterType<IPluginConfigurator, MemoryConfigurator>();
            RootContainer.RegisterInstance<IEventProcessorConfigurator>(mc);
            RootContainer.RegisterInstance<IPluginConfigurator>(mc);
            MockEventProcessorWorker.Workers.Clear();
        }
        //
        //Use TestCleanup to run code after each test has run
        //[TestCleanup()]
        //public void MyTestCleanup()
        //{
        //}
        //
        #endregion

        public Guid[] CustomerIdList = new[]{
                                               Guid.NewGuid(),
                                               Guid.NewGuid(),
                                               Guid.NewGuid(),
                                               Guid.NewGuid(),
                                               Guid.NewGuid(),
                                           };


        ///<summary>
        ///Thread size 2, customer count 2, message count 3
        ///</summary>
        [TestMethod()]
        public void EventQueueWorker_Init1_2_3()
        {
            ProcessorEventQueueWorker sut = RootContainer.Resolve<ProcessorEventQueueWorker>();
            SetupTestData(sut, 1,
                          new CreateCustomerEvent
                          {
                              Id = CustomerIdList[0],
                              Name = "Teszt Pista",
                          },
                          new CreateCustomerEvent
                          {
                              Id = CustomerIdList[1],
                              Name = "Lapos Elemér",
                          },
                          new ChangeCustomerNameEvent
                          {
                              Id = CustomerIdList[1],
                              Name = "Lapos Tihamér",
                          }
                );

            Assert.AreEqual(3, MockEventProcessorWorker.Workers[0].GetQueueSize());
            Assert.AreEqual(2, MockEventProcessorWorker.Workers[0].GetEntityIdList().Distinct().Count());
        }

        ///<summary>
        ///Thread size 2, customer count 2, message count 3
        ///</summary>
        [TestMethod()]
        public void EventQueueWorker_Init2_2_3()
        {
            ProcessorEventQueueWorker sut = RootContainer.Resolve<ProcessorEventQueueWorker>();
            SetupTestData(sut, 2,
                          new CreateCustomerEvent
                              {
                                  Id = CustomerIdList[0],
                                  Name = "Teszt Pista",
                              },
                          new CreateCustomerEvent
                              {
                                  Id = CustomerIdList[1],
                                  Name = "Lapos Elemér",
                              },
                          new ChangeCustomerNameEvent
                              {
                                  Id = CustomerIdList[1],
                                  Name = "Lapos Tihamér",
                              }
                );

            Assert.AreEqual(1, MockEventProcessorWorker.Workers[0].GetQueueSize());
            Assert.AreEqual("Customer|" + CustomerIdList[0], MockEventProcessorWorker.Workers[0].GetEntityIdList().Distinct().Single());
            Assert.AreEqual(2, MockEventProcessorWorker.Workers[1].GetQueueSize());
            Assert.AreEqual("Customer|" + CustomerIdList[1], MockEventProcessorWorker.Workers[1].GetEntityIdList().Distinct().Single());
        }

        ///<summary>
        ///Thread size 2, customer count 2, message count 3
        ///</summary>
        [TestMethod()]
        public void EventQueueWorker_Init2_3_3()
        {
            ProcessorEventQueueWorker sut = RootContainer.Resolve<ProcessorEventQueueWorker>();
            SetupTestData(sut, 2,
                          new CreateCustomerEvent
                          {
                              Id = CustomerIdList[0],
                              Name = "Teszt Pista",
                          },
                          new CreateCustomerEvent
                          {
                              Id = CustomerIdList[1],
                              Name = "Lapos Elemér",
                          },
                          new ChangeCustomerNameEvent
                          {
                              Id = CustomerIdList[1],
                              Name = "Lapos Tihamér",
                          },
                          new CreateCustomerEvent
                              {
                                  Id = CustomerIdList[2],
                                  Name = "Varjú Géza"
                              }
                );

            Assert.AreEqual(2, MockEventProcessorWorker.Workers[0].GetQueueSize());
            Assert.AreEqual(2, MockEventProcessorWorker.Workers[0].GetEntityIdList().Distinct().Count());
            Assert.AreEqual(2, MockEventProcessorWorker.Workers[1].GetQueueSize());
            Assert.AreEqual("Customer|" + CustomerIdList[1], MockEventProcessorWorker.Workers[1].GetEntityIdList().Distinct().Single());
        }

        ///<summary>
        ///Thread size 2, customer count 2, message count 3
        ///</summary>
        [TestMethod()]
        public void EventQueueWorker_InitPulse_2_3_3()
        {
            ProcessorEventQueueWorker sut = RootContainer.Resolve<ProcessorEventQueueWorker>();
            SetupTestData(sut, 2,
                          new CreateCustomerEvent
                          {
                              Id = CustomerIdList[0],
                              Name = "Teszt Pista",
                          },
                          new CreateCustomerEvent
                          {
                              Id = CustomerIdList[1],
                              Name = "Lapos Elemér",
                          },
                          new CreateCustomerEvent
                          {
                              Id = CustomerIdList[2],
                              Name = "Varjú Géza"
                          }
                );

            Assert.AreEqual(2, MockEventProcessorWorker.Workers[0].GetQueueSize());
            Assert.AreEqual(2, MockEventProcessorWorker.Workers[0].GetEntityIdList().Distinct().Count());
            Assert.AreEqual(1, MockEventProcessorWorker.Workers[1].GetQueueSize());
            Assert.AreEqual(1, MockEventProcessorWorker.Workers[1].GetEntityIdList().Distinct().Count());

            MockEventProcessorWorker.Workers.ForEach(i =>
                                                         {
                                                             i.ProcessedEvent.Reset();
                                                             i.PulseEvent.Set();
                                                         });

            // Guess what, WaitAll is not supported in STA model
            MockEventProcessorWorker.Workers.ForEach(i => i.ProcessedEvent.WaitOne(1000));


            PostMessages(sut,
                        true,
                        new ChangeCustomerNameEvent
                          {
                              Id = CustomerIdList[2],
                              Name = "Teszt Márton",
                          },
                        new ChangeCustomerNameEvent
                          {
                              Id = CustomerIdList[1],
                              Name = "Lapos Tihamér",
                          },
                        new ChangeCustomerNameEvent
                          {
                              Id = CustomerIdList[0],
                              Name = "Végh István",
                          }
                );

            Assert.AreEqual(2, MockEventProcessorWorker.Workers[0].GetQueueSize());
            Assert.AreEqual(1, MockEventProcessorWorker.Workers[0].GetEntityIdList().Distinct().Count());
            Assert.AreEqual(2, MockEventProcessorWorker.Workers[1].GetQueueSize());
            Assert.AreEqual(2, MockEventProcessorWorker.Workers[1].GetEntityIdList().Distinct().Count());
        }

        private void SetupTestData(ProcessorEventQueueWorker sut, int threadPoolSize, params EventBase[] messages)
        {
            var mc = RootContainer.Resolve<IPluginConfigurator>() as MemoryConfigurator;
            mc.ThreadCount = threadPoolSize;
            sut.Configure(Guid.NewGuid().ToString());

            sut.InputEventQueue = RootContainer.Resolve<IEventQueue>();
            sut.InputEventQueue.Configure(new TestQueueConfigurator(), "in");
            sut.OutputEventQueue = RootContainer.Resolve<IEventQueue>();
            sut.OutputEventQueue.Configure(new TestQueueConfigurator(), "out");
            //sut.InputEventQueue.Enqueue(new InitWorker
            //                                {
            //                                    Initializers = new List<string>
            //                                                       {
            //                                                           typeof (PluginEntryPoint).AssemblyQualifiedName
            //                                                       },
            //                                    ThreadPoolSize = threadPoolSize,
            //                                }
            //    );

            PostMessages(sut, false, messages);

            //Assert.IsNull(sut.Workers);
            //Assert.IsTrue(sut.DoWork());
            Assert.AreEqual(threadPoolSize, sut.Workers.Count);

            for (int i = 0; i < messages.Length; i++)
            {
                Assert.IsTrue(sut.DoWork());
            }
            Assert.IsFalse(sut.DoWork());
        }

        private static void PostMessages(ProcessorEventQueueWorker sut, bool process, params EventBase[] messages)
        {
            for (int i = 0; i < messages.Length; i++)
            {
                var m = messages[i];
                m.EventId = Guid.NewGuid();
                m.EventDate = DateTime.Now.AddHours(-1).AddMinutes(i);
                //m.SubmittedBy = Guid.NewGuid().ToString();
                sut.InputEventQueue.Enqueue(m);
            }

            if (process)
            {
                for (int i = 0; i < messages.Length; i++)
                {
                    Assert.IsTrue(sut.DoWork());
                }
                Assert.IsFalse(sut.DoWork());
            }
        }
    }
}
